// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/trace_event/memory_allocator_dump.h"

#include <stdint.h>

#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/memory_allocator_dump_guid.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/memory_dump_session_state.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event_argument.h"
#include "base/values.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace trace_event {

    namespace {

        class FakeMemoryAllocatorDumpProvider : public MemoryDumpProvider {
        public:
            bool OnMemoryDump(const MemoryDumpArgs& args,
                ProcessMemoryDump* pmd) override
            {
                MemoryAllocatorDump* root_heap = pmd->CreateAllocatorDump("foobar_allocator");

                root_heap->AddScalar(MemoryAllocatorDump::kNameSize,
                    MemoryAllocatorDump::kUnitsBytes, 4096);
                root_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount,
                    MemoryAllocatorDump::kUnitsObjects, 42);
                root_heap->AddScalar("attr1", "units1", 1234);
                root_heap->AddString("attr2", "units2", "string_value");
                root_heap->AddScalarF("attr3", "units3", 42.5f);

                MemoryAllocatorDump* sub_heap = pmd->CreateAllocatorDump("foobar_allocator/sub_heap");
                sub_heap->AddScalar(MemoryAllocatorDump::kNameSize,
                    MemoryAllocatorDump::kUnitsBytes, 1);
                sub_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount,
                    MemoryAllocatorDump::kUnitsObjects, 3);

                pmd->CreateAllocatorDump("foobar_allocator/sub_heap/empty");
                // Leave the rest of sub heap deliberately uninitialized, to check that
                // CreateAllocatorDump returns a properly zero-initialized object.

                return true;
            }
        };

        std::unique_ptr<Value> CheckAttribute(const MemoryAllocatorDump* dump,
            const std::string& name,
            const char* expected_type,
            const char* expected_units)
        {
            std::unique_ptr<Value> raw_attrs = dump->attributes_for_testing()->ToBaseValue();
            DictionaryValue* args = nullptr;
            DictionaryValue* arg = nullptr;
            std::string arg_value;
            const Value* out_value = nullptr;
            EXPECT_TRUE(raw_attrs->GetAsDictionary(&args));
            EXPECT_TRUE(args->GetDictionary(name, &arg));
            EXPECT_TRUE(arg->GetString("type", &arg_value));
            EXPECT_EQ(expected_type, arg_value);
            EXPECT_TRUE(arg->GetString("units", &arg_value));
            EXPECT_EQ(expected_units, arg_value);
            EXPECT_TRUE(arg->Get("value", &out_value));
            return out_value ? out_value->CreateDeepCopy() : std::unique_ptr<Value>();
        }

        void CheckString(const MemoryAllocatorDump* dump,
            const std::string& name,
            const char* expected_type,
            const char* expected_units,
            const std::string& expected_value)
        {
            std::string attr_str_value;
            auto attr_value = CheckAttribute(dump, name, expected_type, expected_units);
            EXPECT_TRUE(attr_value->GetAsString(&attr_str_value));
            EXPECT_EQ(expected_value, attr_str_value);
        }

        void CheckScalar(const MemoryAllocatorDump* dump,
            const std::string& name,
            const char* expected_units,
            uint64_t expected_value)
        {
            CheckString(dump, name, MemoryAllocatorDump::kTypeScalar, expected_units,
                StringPrintf("%" PRIx64, expected_value));
        }

        void CheckScalarF(const MemoryAllocatorDump* dump,
            const std::string& name,
            const char* expected_units,
            double expected_value)
        {
            auto attr_value = CheckAttribute(dump, name, MemoryAllocatorDump::kTypeScalar,
                expected_units);
            double attr_double_value;
            EXPECT_TRUE(attr_value->GetAsDouble(&attr_double_value));
            EXPECT_EQ(expected_value, attr_double_value);
        }

    } // namespace

    TEST(MemoryAllocatorDumpTest, GuidGeneration)
    {
        std::unique_ptr<MemoryAllocatorDump> mad(
            new MemoryAllocatorDump("foo", nullptr, MemoryAllocatorDumpGuid(0x42u)));
        ASSERT_EQ("42", mad->guid().ToString());

        // If the dumper does not provide a Guid, the MAD will make one up on the
        // flight. Furthermore that Guid will stay stable across across multiple
        // snapshots if the |absolute_name| of the dump doesn't change
        mad.reset(new MemoryAllocatorDump("bar", nullptr));
        const MemoryAllocatorDumpGuid guid_bar = mad->guid();
        ASSERT_FALSE(guid_bar.empty());
        ASSERT_FALSE(guid_bar.ToString().empty());
        ASSERT_EQ(guid_bar, mad->guid());

        mad.reset(new MemoryAllocatorDump("bar", nullptr));
        const MemoryAllocatorDumpGuid guid_bar_2 = mad->guid();
        ASSERT_EQ(guid_bar, guid_bar_2);

        mad.reset(new MemoryAllocatorDump("baz", nullptr));
        const MemoryAllocatorDumpGuid guid_baz = mad->guid();
        ASSERT_NE(guid_bar, guid_baz);
    }

    TEST(MemoryAllocatorDumpTest, DumpIntoProcessMemoryDump)
    {
        FakeMemoryAllocatorDumpProvider fmadp;
        MemoryDumpArgs dump_args = { MemoryDumpLevelOfDetail::DETAILED };
        ProcessMemoryDump pmd(new MemoryDumpSessionState, dump_args);

        fmadp.OnMemoryDump(dump_args, &pmd);

        ASSERT_EQ(3u, pmd.allocator_dumps().size());

        const MemoryAllocatorDump* root_heap = pmd.GetAllocatorDump("foobar_allocator");
        ASSERT_NE(nullptr, root_heap);
        EXPECT_EQ("foobar_allocator", root_heap->absolute_name());
        CheckScalar(root_heap, MemoryAllocatorDump::kNameSize,
            MemoryAllocatorDump::kUnitsBytes, 4096);
        CheckScalar(root_heap, MemoryAllocatorDump::kNameObjectCount,
            MemoryAllocatorDump::kUnitsObjects, 42);
        CheckScalar(root_heap, "attr1", "units1", 1234);
        CheckString(root_heap, "attr2", MemoryAllocatorDump::kTypeString, "units2",
            "string_value");
        CheckScalarF(root_heap, "attr3", "units3", 42.5f);

        const MemoryAllocatorDump* sub_heap = pmd.GetAllocatorDump("foobar_allocator/sub_heap");
        ASSERT_NE(nullptr, sub_heap);
        EXPECT_EQ("foobar_allocator/sub_heap", sub_heap->absolute_name());
        CheckScalar(sub_heap, MemoryAllocatorDump::kNameSize,
            MemoryAllocatorDump::kUnitsBytes, 1);
        CheckScalar(sub_heap, MemoryAllocatorDump::kNameObjectCount,
            MemoryAllocatorDump::kUnitsObjects, 3);
        const MemoryAllocatorDump* empty_sub_heap = pmd.GetAllocatorDump("foobar_allocator/sub_heap/empty");
        ASSERT_NE(nullptr, empty_sub_heap);
        EXPECT_EQ("foobar_allocator/sub_heap/empty", empty_sub_heap->absolute_name());
        auto raw_attrs = empty_sub_heap->attributes_for_testing()->ToBaseValue();
        DictionaryValue* attrs = nullptr;
        ASSERT_TRUE(raw_attrs->GetAsDictionary(&attrs));
        ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameSize));
        ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameObjectCount));

        // Check that the AsValueInfo doesn't hit any DCHECK.
        std::unique_ptr<TracedValue> traced_value(new TracedValue);
        pmd.AsValueInto(traced_value.get());
    }

// DEATH tests are not supported in Android / iOS.
#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS)
    TEST(MemoryAllocatorDumpTest, ForbidDuplicatesDeathTest)
    {
        FakeMemoryAllocatorDumpProvider fmadp;
        MemoryDumpArgs dump_args = { MemoryDumpLevelOfDetail::DETAILED };
        ProcessMemoryDump pmd(new MemoryDumpSessionState, dump_args);
        pmd.CreateAllocatorDump("foo_allocator");
        pmd.CreateAllocatorDump("bar_allocator/heap");
        ASSERT_DEATH(pmd.CreateAllocatorDump("foo_allocator"), "");
        ASSERT_DEATH(pmd.CreateAllocatorDump("bar_allocator/heap"), "");
        ASSERT_DEATH(pmd.CreateAllocatorDump(""), "");
    }
#endif

} // namespace trace_event
} // namespace base
